using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;

using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.Presence;
using Microsoft.Rtc.Signaling;
using log4net;

namespace Lync.Widget.Library.Presence
{
	/// <summary>
	/// The default UCMA implementation of IPresenceProvider.
	/// Creates a collaboration platfrom and endpoint and then subscribes to the presence updates of the requested contacts
	/// </summary>
	public class UcPresenceProvider : IPresenceProvider
	{
		private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		private ApplicationEndpoint m_applicationEndpoint;
        private RemotePresenceView m_remotePresenceView;

		public event EventHandler<PresenceChangedEventArgs> PresenceChanged;

		public UcPresenceProvider(ApplicationEndpoint endpoint)
		{
			m_applicationEndpoint = endpoint;
		}

		public bool Start()
		{
			return InitializeRemotePresenceView();
		}

		public void SubscribeToPresence(string sipUri)
		{
			if (!IsAlreadySubscribed(sipUri))
			{
				Subscribe(sipUri);
			}
		}

		public void UnsubscribeToPresence(string sipUri)
		{
			if (IsAlreadySubscribed(sipUri))
			{
				Unsubscribe(sipUri);
			}
		}

		private bool IsAlreadySubscribed(string sipUri)
		{
		    if (m_remotePresenceView != null)
			{
				Collection<string> subscriptions = m_remotePresenceView.GetPresentities();

				bool isSubscribed = Enumerable.Any(subscriptions, s => s.Equals(sipUri, StringComparison.InvariantCultureIgnoreCase));
			    Log.DebugFormat(
			        "Checking subscription for: {0}. Current subscriptions number is {1}. All subscribers: {2}",
			        sipUri, subscriptions.Count, string.Join(",", subscriptions));
                
                return isSubscribed;
			}
		   
            return false;
		}

	    private void Subscribe(string sipUri)
		{
			SipUriParser uriParser;
			if (SipUriParser.TryParse(sipUri, out uriParser))
			{
				var subscriptionTargets = new List<RemotePresentitySubscriptionTarget>();
				subscriptionTargets.Add(new RemotePresentitySubscriptionTarget(uriParser.ToString()));
				// Immediately fires NotificationReceived with current presence of targets
                m_remotePresenceView.StartSubscribingToPresentities(subscriptionTargets);
			}
			else
			{
			    Log.DebugFormat("Not a valid SIP uri '{0}. Cannot subscribe.'", sipUri);
			}
		}

		private void Unsubscribe(string sipUri)
		{
			SipUriParser uriParser;
			if (SipUriParser.TryParse(sipUri, out uriParser))
			{
				m_remotePresenceView.StartUnsubscribingToPresentities(new List<string> { sipUri });
			}
		}

		private void RemotePresenceView_PresenceNotificationReceived(object sender, RemotePresentitiesNotificationEventArgs e)
		{
            StringBuilder sb = new StringBuilder("Recieved Lync notification: ");
            List<PresenceSubscription> presences = new List<PresenceSubscription>();
			foreach (var notification in e.Notifications)
			{
				presences.Add(ConvertNotificationToPresence(notification));
			    sb.AppendFormat(
			        "sip: '{0}', state: '{1}' | ",
			        notification.PresentityUri,
			        (notification.AggregatedPresenceState != null ? notification.AggregatedPresenceState.AvailabilityValue.ToString() : "NULL"));
			}

            Log.Debug(sb.ToString());

			if (PresenceChanged != null)
			{
				PresenceChanged(this, new PresenceChangedEventArgs { Presences = presences });
			}
		}

		private PresenceSubscription ConvertNotificationToPresence(RemotePresentityNotification notification)
		{
			//after the first notification for a contact, you only get the categories that changed next time
			//so after the first notification if presence changes, you only get presence; if name changes you only get contact card
			long availability = PresenceSubscription.NoChangeInAvailability;
			string status = PresenceSubscription.NoChange;
			string contactName = PresenceSubscription.NoChange;
			string emailAddress = PresenceSubscription.NoChange;
			string jobTitle = PresenceSubscription.NoChange;
			string phone = PresenceSubscription.NoChange;
			string mobile = PresenceSubscription.NoChange;

			var presence = notification.AggregatedPresenceState;
			if (presence != null)
			{
				availability = presence.AvailabilityValue;
				status = AvailabilityToStatusConverter.Convert(presence.AvailabilityValue);
				if (presence.Activity != null)
				{
					switch (presence.Activity.ActivityToken)
					{
						case "in-a-meeting":
							status = "In a meeting";
							break;
						case "on-the-phone":
							status = "On the phone";
							break;
						case "in-a-conference":
							status = "In a conference call";
							break;

					}
				}
			}

			var contactCard = notification.ContactCard;
			if (contactCard != null)
			{
				contactName = contactCard.DisplayName;
				emailAddress = contactCard.EmailAddress;
				jobTitle = contactCard.JobTitle;
				var workphone = contactCard.PhoneNumbers.SingleOrDefault(p => p.PhoneType == "work");
				if (workphone != null) phone = workphone.Uri.Substring(workphone.Uri.IndexOf(':') + 1);
				var mobilestr = contactCard.PhoneNumbers.SingleOrDefault(p => p.PhoneType == "mobile");
				if (mobilestr != null) mobile = mobilestr.Uri.Substring(mobilestr.Uri.IndexOf(':') + 1);
			}

			return new PresenceSubscription
					{
						Availability = availability,
						ContactName = contactName,
						SipUri = notification.PresentityUri,
						Status = status,
						Email = emailAddress,
						JobTitle = jobTitle,
						Phone = phone,
						Mobile = mobile,
					};
		}

		private bool InitializeRemotePresenceView()
		{
			try
			{
				// RemotePresenceView provides a unified way to request presence for a set of presentities
				var settings = new RemotePresenceViewSettings
					{
						SubscriptionMode = RemotePresenceViewSubscriptionMode.Default,
						PollingInterval = new TimeSpan(0, 0, 5)
					};

                m_remotePresenceView = new RemotePresenceView(m_applicationEndpoint, settings);
				Log.Info("Pooling interval: " + m_remotePresenceView.PollingInterval.TotalMilliseconds);
				// Wire up NotificationReceived before adding targets
				m_remotePresenceView.PresenceNotificationReceived += RemotePresenceView_PresenceNotificationReceived;
                m_remotePresenceView.SubscriptionStateChanged += RemotePresenceView_SubscriptionStateChanged;
				
                Log.Info("UcPresenceProvider started");
				return true;
			}
			catch (Exception ex)
			{
				Log.Error("UCPresenceProvider failed: Could not initialize remote presence view {0}", ex);
				return false;
			}
		}

	    private void RemotePresenceView_SubscriptionStateChanged(object sender, RemoteSubscriptionStateChangedEventArgs e)
	    {
	        foreach (KeyValuePair<RealTimeAddress, RemotePresentityStateChange> pair in e.SubscriptionStateChanges)
	        {
	            Log.WarnFormat(
	                "Subscription for '{0}' changed from state '{1}' to '{2}'. Reson: {3}",
	                pair.Key.Uri,
	                pair.Value.PreviousState,
	                pair.Value.State,
	                pair.Value.TransitionReason);
	        }
	    }
	}
}
